﻿#region Copyright Statement
//  ------------------------------------------------------------------------------
//  
//    This file is part of The Streambolics Library.
//    Copyright © 2005-2009, Stephan Leclercq & Streambolics
//    All rights reserved.
//  
//    The Streambolics Library is free software; you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation; either version 2 of the License, or
//    (at your option) any later version.
//  
//    The Streambolics Library is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//  
//    You should have received a copy of the GNU General Public License
//    along with Streambolics; if not, write to the Free Software
//    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
//  
//  ------------------------------------------------------------------------------
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Globalization;

namespace Streambolics.Hp41s
{
    /// <summary>
    ///     The execution engine of the HP-41 Simulator
    /// </summary>
    /// <remarks><para>
    ///     The execution engine <i>simulates</i> (and so does not <i>emulate</i>) an HP-41C series
    ///     calculator. By <i>simulate</i>, I mean that the engine will function in a very
    ///     similar way to a real calculator, but with many differences, some subtle,
    ///     some less subtle. Among the differences:
    ///     <list type="bullet"><item>
    ///         Many limitations due to constrained memory no longer apply: 
    ///         <list type="bullet"><item>
    ///             The <b>SIZE</b> parameter giving the number of data registers is no longer limited to 319
    ///             (there is however a safeguard limitation to 65536 data registers)
    ///         </item><item>
    ///             Programs can be of unlimited size.
    ///         </item><item>
    ///             Extended memory is of unlimited size.
    ///         </item></list>
    ///     </item><item>
    ///         Many limitations due to the data encoding no longer apply:
    ///         <list type="bullet"><item>
    ///             Indices for <b>LBL</b> and <b>RCL</b>/<b>STO</b> are no longer limited to the range
    ///             00-99: you can write the instruction <b>RCL 4095</b>.
    ///         </item><item>
    ///             In addition to numerical labels and registers, one-letter labels and registers are
    ///             possible. Each of the 26 letters can now be used, contrary to the HP-41C, that could
    ///             only use A-J and a-e for labels, and X, Y, Z, T and L for registers. Labels and registers
    ///             A-Z and a-z are now useable. While this allows the simulator to accept a wider range
    ///             of arguments to some instructions, and thus provide simulation for other machines, the
    ///             choice is always to favor HP-41C over other models:
    ///             <list type="bullet"><item>
    ///                 The HP-67 has registers A-E and I which can be accessed as registers number 20 to
    ///                 25 using indirect addressing. This is not the case in the simulator, where register
    ///                 numbered 20 is not the same as A.
    ///             </item><item>
    ///                 Similarly, indirect access to alphabetical labels on the HP-67 uses still another
    ///                 representation.
    ///             </item><item>
    ///                 Registers X, Y, Z, T and L correspond to the operand stack. This may pose compatibility
    ///                 issues when typing or importing HP-33S or HP-35S for which these variable names are
    ///                 independant from the operand stack.
    ///             </item><item>
    ///                 The registers A-Z etc. can be accessed indirectly by providing negative values (for
    ///                 example, if register <c>00</c> contains the value -1, the instruction <c>RCL IND 00</c>
    ///                 will push the value of the Z register on the operand stack. On a real HP-41C this
    ///                 would generate a <c>NONEXISTENT</c> error (or, for very early models, retrieve other
    ///                 parts of memory, including programs and assignations.
    ///             </item></list>
    ///         </item><item>
    ///             Constant strings are no longer limited to 16 characters. There is still a
    ///             limitation to 24 characters to the Alpha register, for compatibility reasons.
    ///         </item><item>
    ///             Numbers in data registers and the operand stack are expressed as a double precision
    ///             IEEE floating point number, which has greater precision and range than the
    ///             HP-41C native floating point numbers.
    ///         </item><item>
    ///             Out-of-range errors do not generate the same results when flag 24 is set. The
    ///             simulator uses NAN and infinites instead.
    ///         </item><item>
    ///             Strings in data registers and the operand stack can be longer than 6 characters. However, 
    ///             for compatibility reasons, the <b>ASTO</b> instructions will still only copy the first 6 
    ///             characters to the data register.
    ///         </item><item>
    ///             Global labels and file names are no longer limited to 7 characters.
    ///         </item></list>
    ///     </item><item>
    ///         Execution speed is an order of magnitude faster than a real HP-41C. Using hard-coded loops
    ///         to provide delay would be useless.
    ///     </item><item>
    ///         Error messages may differ, especially regarding arithmetic errors. The simulator uses
    ///         exceptions to represent HP-41C errors. Especially when using arithmetic operations, the
    ///         standard CLI exceptions are used.
    ///     </item><item>
    ///         Synthetic programming is not supported. Although a library could be written that emulates
    ///         most of synthetic programming possibilities, many features would be impossibly complex to
    ///         write, particularly the <c>eGOBEEP</c> instruction since no one knows what it does.
    ///     </item></list>
    /// </para></remarks>
    
    public class Engine : Data
    {
        private Context _CurrentContext = null;
        private Registers _Registers = new Registers ();
        private string _Display;
        private int _Bird = 0;
        private Flags _Flags = new Flags ();

        private UserKeyboardMapping _PlainUserKeyboard = new UserKeyboardMapping (false);
        private UserKeyboardMapping _ShiftedUserKeyboard = new UserKeyboardMapping (true);

        public Engine ()
            : base ("Engine")
        {
            new RootContext (this);
        }

        #region Execution Management

        internal void PushContext (Context aContext)
        {
            Debug.Assert (aContext.Parent == _CurrentContext);
            if (_CurrentContext != null)
            {
                _CurrentContext.ContextSwitch ();
            }
            _CurrentContext = aContext;
        }

        internal void PopContext ()
        {
            Debug.Assert (_CurrentContext.Parent != null);
            _CurrentContext.ContextSwitch ();
            _CurrentContext = _CurrentContext.Parent;
        }

        /// <summary>
        ///     Execute a single instruction and all its consequences.
        /// </summary>
        /// <param name="aInstruction">
        ///     The instruction to execute
        /// </param>
        /// <remarks><para>
        ///     Althoug a single instruction is executed, this instruction
        ///     may have side effects that will execute more instructions.
        ///     For example, executing a <b>XEQ</b> instruction will execute the
        ///     whole subprogram. Similarly, executing a <b>R/S</b> instruction
        ///     while the program is idle will resume execution of a stopped
        ///     program.
        /// </para></remarks>

        public void Execute (Instruction aInstruction)
        {
            _CurrentContext.Execute (aInstruction);
            while (_CurrentContext.Run ())
            {
                RotateDisplay ();
            }
        }

        public void Execute (Steps aSteps)
        {
            foreach (Instruction i in aSteps.Instructions)
            {
                Execute (i);
            }
        }
        #endregion

        #region Display Management

        private void RotateDisplay ()
        {
            if (_Display == null)
            {
                _Bird++;
                DoDisplayChanged ();
            }
        }

        /// <summary>
        ///     The data currently displayed on the LCD screen.
        /// </summary>
        /// <remarks><para>
        ///     This is not constrained to 12 characters (excluding optional punctuation)
        ///     as on a real HP-41C.
        /// </para></remarks>

        public string Display
        {
            get
            {
                if (_Display == null)
                {
                    return _CurrentContext.GetDisplay ();
                }
                else
                {
                    return _Display;
                }
            }
            set
            {
                if (value != null)
                {
                    _Display = value;
                    DoDisplayChanged ();
                }
            }
        }

        public Value DisplayValue
        {
            get
            {
                if (_Display == null)
                {
                    return _CurrentContext.GetDisplayValue ();
                }
                else
                {
                    return new StringValue (_Display);
                }
            }
        }
        /// <summary>
        ///     Clears the display
        /// </summary>

        public void Cld ()
        {
            if (_Display != null)
            {
                _Display = null;
                DoDisplayChanged ();
            }
        }

        internal string GetBirds ()
        {
            _Bird %= 12;
            return "            ".Substring (0, _Bird) + "‡";
        }


        /// <summary>
        ///     Present the value as a 41 would do.
        /// </summary>
        /// <param name="aValue">
        ///     The value to be presented.
        /// </param>
        /// <returns>
        ///     A string representation of the value
        /// </returns>
        /// <remarks><para>
        ///     The engine uses the NumberFormatInfo property to format
        ///     a number. This property in turn depends on the status flags.
        /// </para></remarks>

        public string Present (Value aValue)
        {
            return aValue.ToString (_Flags);
        }

        #endregion
        
        /// <summary>
        ///     The data registers and operand stack.
        /// </summary>

        public Registers Registers
        {
            get
            {
                return _Registers;
            }
        }

        /// <summary>
        ///     The long listing of the current program.
        /// </summary>
        /// <returns>
        ///     A string representing the full long listing of the current
        ///     program.
        /// </returns>

        public string LongListing ()
        {
            return _CurrentContext.LongListing ();
        }

        /// <summary>
        ///     Signals that the Display property has changed and needs
        ///     to be redisplayed.
        /// </summary>

        public event EventHandler DisplayChanged;

        protected void OnDisplayChanged (EventArgs e)
        {
            if (DisplayChanged != null)
            {
                DisplayChanged (this, e);
            }
        }

        internal protected void DoDisplayChanged ()
        {
            OnDisplayChanged (EventArgs.Empty);
        }

        public Flags Flags
        {
            get
            {
                return _Flags;
            }
        }

        public void KeyClick (int aKey)
        {
            try
            {
                _CurrentContext.KeyClick (aKey);
            }
            catch (Exception e)
            {
                _CurrentContext.DoPrompt (e.Message);
            }
        }

        internal KeyboardMapping Keyboard ()
        {
            if (Flags.Alpha)
            {
                if (Flags.Shift)
                {
                    return StandardKeyboards.ShiftedAlpha;
                }
                else
                {
                    return StandardKeyboards.Alpha;
                }
            }
            else if (Flags.UserMode)
            {
                if (Flags.Shift)
                {
                    return _ShiftedUserKeyboard;
                }
                else
                {
                    return _PlainUserKeyboard;
                }
            }
            else if (Flags.Shift)
            {
                return StandardKeyboards.Shifted;
            }
            else
            {
                return StandardKeyboards.Plain;
            }
            throw new NotImplementedException ();
        }
    }
}
